function testLog() {
    baseLog("/////////////////////////////////////////////////////////////////");
    baseLog("Hello chapter-6.js!");
}

function testObject() {
    {
        let o1 = new Object();          // The same as let o1 = {};
        o1.name = 'o1';
        o1.age = 29;
        let o2 = {
            name: 'o2',
            age: 20
        }
        function display(args) {
            let output = '';
            if (typeof args.name == 'string') {
                output += 'Name: ' + args.name + '\n';
            }
            if (typeof args.age == 'number') {
                output += 'Age: ' + args.age + '\n';
            }
            baseLog(output);
        }
        display(o1);
        display(o2);
        display({name: 'o3'});
    }
}

function testArray() {
    {
        let a1 = ['red', 'blue', 'green'];
        let a2 = new Array('red', 'blue', 'green');
        baseLog(Array.from('abc'));             // ['a', 'b', 'c']
        const m = new Map().set(1, 2).set(3, 4);
        const s = new Set().add(1).add(2).add(3).add(4);
        baseLog(Array.from(m));                 // [[1,2],[3,4]]
        baseLog(Array.from(s));                 // [1,2,3,4]
        const a3 = [1, 2, 3];
        const a4 = Array.from(a3);
        baseLog(a3 == a4);                      // false
        const iter = {
            *[Symbol.iterator]() {
                yield 1;
                yield 2;
                yield 3;
                yield 4;
            }
        };
        baseLog(Array.from(iter));              // [1, 2, 3, 4]
        function getArgsArray() {
            return Array.from(arguments);
        }
        baseLog(getArgsArray(1, 2, 3, 4));   // [1, 2, 3, 4]
        const arrayLikeObject = {
            0: 1,
            1: 2,
            length: 2
        };
        baseLog(Array.from(arrayLikeObject));       // [1, 2]
        const arr = [1, 2, 3, 4];
        baseLog(Array.from(arr, x => x ** 2));      // [1, 4, 9, 16]
        baseLog(Array.from(arr, function(x) {return x ** this.exponent}, {exponent: 2}));   // [1, 4, 9, 16]
        baseLog(Array.of(1, 2, 3, 4));              // [1, 2, 3, 4]
        baseLog(Array.of(undefined));               // [undefined]
    }
    {
        let arr = ['a', 'b', 'c'];
        arr.length = 2;
        baseLog(arr[2]);                        // undefined
        arr.length = 6;
        baseLog(arr[5]);                        // undefined
        baseLog(Array.isArray(arr));            // true
    }
    {
        const a = ['a', 'b', 'c'];
        const aKeys = Array.from(a.keys());
        const aValues = Array.from(a.values());
        const aEntries = Array.from(a.entries());
        baseLog(aKeys);                         // [0, 1, 2]
        baseLog(aValues);                       // ['a', 'b', 'c']
        baseLog(aEntries);                      // [[0, 'a'], [1, 'b'], [2, 'c']]
        for (const [idx, element] of a.entries()) {
            baseLog(idx + ' ' + element);       // 0 a, 1 b, 2 c
        }
    }
    {
        // mock stack
        let arr = ['a', 'b'];
        arr.push('c');
        baseLog(arr.pop());                     // 'c'
        arr.push('d');
        baseLog(arr.pop());                     // 'd'
        baseLog(arr.length);                    // 2
    }
    {
        // mock queue
        let arr = ['a', 'b'];
        baseLog(arr.push('c'));                 // 3
        baseLog(arr.shift());                   // 'a'
        baseLog(arr.length);                    // 2
    }
    {
        function compare(a, b) {
            if (a < b) {
                return 1;
            } else if (a > b) {
                return -1;
            } else {
                return 0;
            }
        }
        let arr = [0, 1, 5, 10, 15];
        arr.sort(compare);
        baseLog(arr);                           // [15, 10, 5, 1, 0]
        arr.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
        baseLog(arr);                           // [0, 1, 5, 10, 15]
    }
    {
        let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
        baseLog(arr.indexOf(4));                // 3
        baseLog(arr.lastIndexOf(4));            // 5
        baseLog(arr.includes(4));               // true
        baseLog(arr.indexOf(4, 4));             // 5
        baseLog(arr.lastIndexOf(4, 4));         // 3
        baseLog(arr.includes(4, 7));            // false
        /*
            1+0+1,2,3,4,5,4,3,2,1
            2+1+1,2,3,4,5,4,3,2,1
            3+2+1,2,3,4,5,4,3,2,1
        */
        arr.find((element, index, array) => {
            baseLog(element + '+' + index + '+' + array);
            return element == 3;
        });
    }
    {
        let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
        baseLog(arr.every((item, index, array) => item > 2));       // false
        baseLog(arr.some((item, index, array) => item > 2));        // true
        baseLog(arr.filter((item, index, array) => item > 2));      // [3, 4, 5, 4, 3]
        baseLog(arr.map((item, index, array) => item * 2));         // [2, 4, 6, 8, 10, 8, 6, 4, 2]
        arr.forEach((item, index, array) => {
            // do something
        });
        baseLog(arr.reduce((prev, cur, index, array) => prev + cur));           // 25
        baseLog(arr.reduceRight((prev, cur, index, array) => prev + cur));      // 25
    }
}

function testDefinitiveArray() {
    {
        const buf = new ArrayBuffer(16);
        const fullDataView = new DataView(buf);
        baseLog(fullDataView.byteOffset);                   // 0
        baseLog(fullDataView.byteLength);                   // 16
        baseLog(fullDataView.buffer === buf);               // true
        const firstHalf = new DataView(buf, 0, 8);
        baseLog(firstHalf.byteOffset);                      // 0
        baseLog(firstHalf.byteLength);                      // 8
        baseLog(firstHalf.buffer === buf);                  // true
        const secondHalf = new DataView(buf, 8);
        baseLog(secondHalf.byteOffset);                     // 0
        baseLog(secondHalf.byteLength);                     // 8
        baseLog(secondHalf.buffer === buf);                 // true
    }
    {
        const buf = new ArrayBuffer(2);
        const view = new DataView(buf);
        baseLog(view.getInt8(0));                   // 0
        baseLog(view.getInt8(1));                   // 0
        baseLog(view.getInt16(0));                  // 0
        view.setUint8(0, 255);
        view.setUint8(1, 0xFF);
        baseLog(view.getInt16(0));                  // -1
        // 0x8 0x0 0x0 0x1 => 1000000000000001
        view.setUint8(0, 0x80);
        view.setUint8(1, 0x01);
        baseLog(view.getUint16(0));                 // 32769, read in big-end byte order: 0x8001 = 2 ^ 15 + 2 ^ 0 = 32768 + 1 = 32769
        baseLog(view.getUint16(0, true));           // 384, read in little-end byte order: 0x180 = 2 ^ 8 + 2 ^ 7 = 256+126 = 384
        view.setUint16(0, 0x0004);                      // write in big-end byte order, 0x0 0x0 0x0 0x4 => 0000000000000100
        baseLog(view.getUint8(0));                  // 0
        baseLog(view.getUint8(1));                  // 4
        view.setUint16(0, 0x0002, true);                // write in little-end byte order, 0x0 0x2 0x0 0x0 => 0000001000000000
        baseLog(view.getUint8(0));                  // 2
        baseLog(view.getUint8(1));                  // 0
    }
    {
        const buf = new ArrayBuffer(6);
        const view = new DataView(buf);
        // view.getInt32(4);                               // RangeError
        // view.getInt32(-1);                              // RangeError
        // view.setInt32(4, 132);                          // RangeError
    }
}

function testMap() {
    {
        const m1 = new Map([['k1', 'v1'], ['k2', 'v2'], ['k3', 'v3']]);
        baseLog(m1.size);                               // 3
        const m2 = new Map({
            [Symbol.iterator]: function*() {
                yield ['k1', 'v1'];
                yield ['k2', 'v2'];
                yield ['k3', 'v3'];
            }
        });
        baseLog(m2.size);                               // 3
    }
    {
        const m = new Map();
        baseLog(m.has('k1'));                           // false
        baseLog(m.get('k1'));                           // undefined
        baseLog(m.size);                                // 0
        m.set('k1', 'v1').set('k2', 'v2');
        baseLog(m.has('k1'));                           // true
        baseLog(m.get('k1'));                           // 'v1'
        baseLog(m.size);                                // 2
        m.delete('k1');
        baseLog(m.has('k1'));                           // false
        baseLog(m.has('k2'));                           // true
        baseLog(m.size);                                // 1
        m.clear();
        baseLog(m.has('k1'));                           // false
        baseLog(m.has('k2'));                           // false
        baseLog(m.size);                                // 0
    }
    {
        const m = new Map();
        const functionKey = function() {};
        const symbolKey = Symbol();
        const objectKey = new Object();
        m.set(functionKey, 'functionValue');
        m.set(symbolKey, 'symbolValue');
        m.set(objectKey, 'obejctValue');
        baseLog(m.get(functionKey));                    // 'functionValue'
        baseLog(m.get(symbolKey));                      // 'symbolValue'
        baseLog(m.get(objectKey));                      // 'obejctValue'
        baseLog(m.get(function() {}));                  // undefined
    }
    {
        const m = new Map([
            ['k1', 'v1'],
            ['k2', 'v2'],
            ['k3', 'v3']
        ]);
        baseLog(m.entries === m[Symbol.iterator]);          // true
        for (let pair of m.entries()) {
            baseLog(pair);                                  // ['k1', 'v1'], ['k2', 'v2'], ['k3', 'v3']
        }
        for (let pair of m[Symbol.iterator]()) {
            baseLog(pair);                                  // ['k1', 'v1'], ['k2', 'v2'], ['k3', 'v3']
        }
        baseLog([...m]);                                    // [['k1', 'v1'], ['k2', 'v2'], ['k3', 'v3']]
        m.forEach((val, key) => baseLog(`${key} -> ${val}`));   // k1 -> v1, k2 -> v2, k3 -> v3
    }
}

function testWeakMap() {
    {
        const k1 = {id: 1},
              k2 = {id: 2},
              k3 = {id: 3};
        const wm1 = new WeakMap([
            [k1, 'v1'],
            [k2, 'v2'],
            [k3, 'v3']
        ]);
        baseLog(wm1.get(k1));               // 'v1'
        // const wm2 = new WeakMap([['BADKEY', 'val']]);           // TypeError: Invalid value used as WeakMap key
        const stringKey = new String('k1');
        const wm3 = new WeakMap([[stringKey, 'val']]);
        baseLog(wm3.get(stringKey));        // 'val'
    }
    {
        const User = (() => {
            const wm = new WeakMap();
            class User {
                constructor(id) {
                    this.idProperty = Symbol('id');
                    this.setId(id);
                }
                setPrivate(property, value) {
                    const privateMembers = wm.get(this) || {};
                    privateMembers[property] = value;
                    wm.set(this, privateMembers);
                }
                getPrivate(property) {
                    return wm.get(this)[property];
                }
                setId(id) {
                    this.setPrivate(this.idProperty, id);
                }
                getId() {
                    return this.getPrivate(this.idProperty);
                }
            }
            return User;
        })();
        const user = new User(123);
        baseLog(user.getId());                  // 123
        user.setId(456);
        baseLog(user.getId());                  // 456
    }
    {
        // can used to storage the DOM object.
        // const wm = new WeakMap();
        // const loginButton = document.querySelector('#login');
        // wm.set(loginButton, {disabled: true});
    }
}

function testSet() {
    {
        const s1 = new Set(['v1', 'v2']);
        baseLog(s1.size);                     // 2
        const s2 = new Set({
            [Symbol.iterator]: function*() {
                yield 'v1';
                yield 'v2';
            }
        });
        baseLog(s2.size);                       // 2
    }
    {
        const s = new Set();
        baseLog(s.has('v1'));                   // false
        s.add('v1').add('v2');
        baseLog(s.has('v1'));                   // true
        baseLog(s.delete('v1'));                // true
        baseLog(s.delete('v1'));                // false
        baseLog(s.has('v1'));                   // false
        baseLog(s.has('v2'));                   // true
        s.clear();
        baseLog(s.has('v2'));                   // false
    }
    {
        const s = new Set();
        const objVal = {}, arrVal = [];
        s.add(objVal).add(arrVal);
        objVal.bar = 'bar';
        arrVal.push('bar');
        baseLog(s.has(objVal));                 // true
        baseLog(s.has(arrVal));                 // true
    }
    {
        const s = new Set(['v1', 'v2', 'v3']);
        baseLog(s.values === s[Symbol.iterator]);           // true
        baseLog(s.keys === s[Symbol.iterator]);             // true
        for (let val of s.values()) {
            baseLog(val);                                   // 'v1', 'v2', 'v3'
        }
        for (let val of s[Symbol.iterator]()) {
            baseLog(val);                                   // 'v1', 'v2', 'v3'
        }
        baseLog([...s]);                                    // ['v1', 'v2', 'v3']
        for (let pair of s.entries()) {
            baseLog(pair);                                  // [['v1', 'v1'], ['v2', 'v2'], ['v3', 'v3']]
        }
        s.forEach((val, dupVal) => baseLog(`${val} -> ${dupVal}`));     // 'v1 -> v1', 'v2 -> v2', 'v3 -> v3'
    }
    {
        class XSet extends Set {
            union(...sets) {
                return XSet.union(this, ...sets);
            }
            intersection(...sets) {
                return XSet.intersection(this, ...sets);
            }
            difference(set) {
                return XSet.difference(this, set);
            }
            symmetricDifference(set) {
                return XSet.symmetricDifference(this, set);
            }
            cartesianProduct(set) {
                return XSet.cartesianProduct(this, set);
            }
            powerSet() {
                return XSet.powerSet(this);
            }
            static union(a, ...bSets) {
                const unionSet = new XSet(a);
                for (const b of bSets) {
                    for (const bVal of b) {
                        unionSet.add(bVal);
                    }
                }
                return unionSet;
            }
            static intersection(a, ...bSets) {
                const intersectionSet = new XSet(a);
                for (const aVal of intersectionSet) {
                    for (const b of bSets) {
                        if (!b.has(aVal)) {
                            intersectionSet.delete(aVal);
                        }
                    }
                }
                return intersectionSet;
            }
            static difference(a, b) {
                const differenceSet = new XSet(a);
                for (const bVal of b) {
                    if (a.has(bVal)) {
                        differenceSet.delete(bVal);
                    }
                }
                return differenceSet;
            }
            static symmetricDifference(a, b) {
                return a.union(b).difference(a.intersection(b));
            }
            static cartesianProduct(a, b) {
                const cartesianProductSet = new XSet();
                for (const aVal of a) {
                    for (const bVal of b) {
                        cartesianProductSet.add([aVal, bVal]);
                    }
                }
                return cartesianProductSet;
            }
            static powerSet(a) {
                const powerSet = new XSet().add(new XSet());
                for (const aVal of a) {
                    for (const set of new XSet(powerSet)) {
                        powerSet.add(new XSet(set).add(aVal));
                    }
                }
                return powerSet;
            }
        }
    }
}

function testWeakSet() {
    {
        const val1 = {id: 1},
              val2 = {id: 2};
        const ws = new WeakSet([val1, val2]);
        baseLog(ws.has(val1));                      // true
    }
    {
        // used to storage the DOM object.
        // const ws = new WeakSet();
        // const loginButton = document.querySelector('#login');
        // ws.add(loginButton);
    }
}

function testIterationAndExtension() {
    {
        let iterableThings = [
            Array.of(1, 2),
            typeArr = Int16Array.of(3, 4),
            new Map([[5, 6], [7, 8]]),
            new Set([9, 10])
        ];
        for (const iter of iterableThings) {
            for (const x of iter) {
                baseLog(x);                 // 1,2,3,4,[5,6],[7,8],9,10
            }
        }
    }
    {
        let arr1 = [1, 2, 3];
        let arr2 = [...arr1];
        baseLog(arr1);                          // [1,2,3]
        baseLog(arr2);                          // [1,2,3]
        baseLog(arr1 == arr2);                  // false
        baseLog(arr1 === arr2);                 // false
        let arr3 = [0, ...arr1, 4, 5];
        baseLog(arr3);                          // [0,1,2,3,4,5]
    }
    {
        let arr1 = [{}];
        let arr2 = [...arr1];
        arr1[0].foo = 'bar';
        baseLog(arr2[0]);                       // {foo: 'bar}
    }
    {
        let arr1 = [1, 2, 3];
        let typeArr1 = Int16Array.of(...arr1);
        let typeArr2 = Int16Array.from(arr1);
        let map = new Map(arr1.map((x) => [x, 'val' + x]));
        let set = new Set(typeArr2);
        let arr2 = [...set];
    }
}


///////////////////////////////////////////////////////////////////////////////////////
testLog();
testObject();
testArray();
testDefinitiveArray();
testMap();
testWeakMap();
testSet();
testWeakSet();
testIterationAndExtension();
